# -*- coding: utf-8 -*-
"""
@author:xuyuntao
@time:2021/4/19:14:45
@email:xuyuntao@189.cn
"""
import numpy

from Generator import *
from charactersAlgorithm import f_x1,f_x2,f_x3
from charactersAlgorithm.instInf import *
from charactersAlgorithm import getCharaReco,c41c202,fskCon
from Tools import MT,getBias
import tqdm
"""调制识别的程序"""
def reco_ask(f_x1, idealValue:numpy.ndarray=None, freeBias:float=3.0,showMsg:bool=True):
    """利用f_x1参数对2ASK/4ASK/8ASK进行类内分类"""
    if showMsg:
        print("f_x1=",f_x1)
    if type(idealValue)==numpy.ndarray:
        pass
    else:
        idealValue=numpy.array([32.0, 27.48, 27.17],dtype=numpy.float64)
    askStep=numpy.array([2,4,8],dtype=numpy.int8)
    Bias=getBias(idealValue,freeBias)
    F_x1_arr=numpy.ones(3,dtype=numpy.float64)*f_x1
    judge_1 = (idealValue + Bias[1,:]) >= F_x1_arr
    judge_2 = (idealValue - Bias[0,:]) <= F_x1_arr
    judge=numpy.logical_and(judge_1,judge_2)
    step=askStep[judge]
    if step.size>=1:
        return MT("ask",int(step[0]))
    else:
        return None

def reco_qam(f_x1, idealValue:numpy.ndarray=None, freeBias:float=2.0,showMsg:bool=True):
    """利用f_x1参数对16QAM/64QAM/256QAM进行类内分类"""
    if showMsg:
        print("f_x1=",f_x1)
    if type(idealValue)==numpy.ndarray:
        pass
    else:
        idealValue=numpy.array([13.95, 13.7590,13.6454],dtype=numpy.float64)
    qamStep=numpy.array([16,64,256],dtype=numpy.int16)
    Bias=getBias(idealValue,freeBias)
    # F_x1=abs(f_x1(constel))
    F_x1_arr=numpy.ones(3,dtype=numpy.float64)*f_x1  # fix#me:qam调制添加噪声后的f_X1与理论值偏差较大，需要修正
    judge_1 = (idealValue + Bias[1,:]) >= F_x1_arr
    judge_2 = (idealValue - Bias[0,:]) <= F_x1_arr
    judge=numpy.logical_and(judge_1,judge_2)
    step=qamStep[judge]
    if step.size>=1:
        return MT("qam",int(step[0]))
    else:
        return None

def recognize(constel:numpy.ndarray, signal:numpy.ndarray, timeSeries,
              carrierFreq:int, seperate:int, a_t:float=1.0, noiseRad:float=0.1*numpy.pi,
              useSigmaafOrc41c202:bool=True,
              f_x2_bias:float=0.1, f_x2_ideal:float=0.0,
              f_x3_bias:float=0.1, f_x3_ideal:float=0.0,
              c41c202_judge:float=6.0, sigma_af_judge:float=0.65,
              C20C21_ideal:float=1.0, C20C21_bias:float=0.1,
              C63C21_ideal:float=4.0, C63C21_bias:float=1.4,
              askIdealValue:numpy.ndarray=None, askFreeBias:float=3.0,
              qamIdealValue:numpy.ndarray=None, qamFreeBias:float=2.0,
              showMsg:bool=True):
    """利用判决理论对高阶累积量进行分类，区分出几种信号种类
    输入：基带信号，调制信号，时间序列，载波频率，fsk频率间隔，论文中的at判决门限，噪声取余门限（建议默认），
    是否使用fsk变换后进行判决，5个特征理想数值及其允许改变范围，
    ask类内识别时使用的理想特征值矩阵（索引从前到后对应2、4、8ask），ask类内识别的理想值的左右偏差值矩阵，
    qam类内识别时使用的理想特征值矩阵（索引从前到后对应16、64、256qam），qam类内识别的理想值的左右偏差值矩阵，
    是否输出log选项
    输出：判决结果（MT类型，包含.step和.type两个标签）"""
    chara=getCharaReco(constel,signal,timeSeries,carrierFreq,a_t,noiseRad)
    if chara.f_x2<=f_x2_ideal+f_x2_bias and chara.f_x2>=f_x2_ideal-f_x2_bias and \
            chara.f_x3<=f_x3_ideal+f_x3_bias and chara.f_x3>=f_x3_ideal-f_x3_bias:
        # 2FSK/4FSK
        if showMsg:
            print("f_x2={0},f_x3={0}，判断为2FSK/4FSK".format(chara.f_x2,chara.f_x3))
        if useSigmaafOrc41c202:
            fskConstel=fskCon(constel,timeSeries,seperate)
            fskc41c202=numpy.abs(c41c202(fskConstel))
            if showMsg:
                print("c41/c20**2=",fskc41c202)
            if fskc41c202<=c41c202_judge:
                # 2FSK
                if showMsg:
                    print("c41c202={}，判断为2FSK".format(fskc41c202))
                return MT("fsk",2)
            else:
                # 4FSK
                if showMsg:
                    print("c41c202={}，判断为4FSK".format(fskc41c202))
                return MT("fsk",4)
        else:
            if chara.sigma_af <= sigma_af_judge:
                # 2FSK
                if showMsg:
                    print("sigma_af={}，判断为2FSK".format(chara.sigma_af))
                return MT("fsk", 2)
            else:
                # 4FSK
                if showMsg:
                    print("sigma_af={}，判断为4FSK".format(chara.sigma_af))
                return MT("fsk", 4)
    else:
        # MPSK/MQAM/MASK
        if showMsg:
            print("C20/C21={}".format(chara.C20/chara.C21))
        if chara.C20/chara.C21<=C20C21_ideal+C20C21_bias and \
                chara.C20/chara.C21>=C20C21_ideal-C20C21_bias:
            # 4ASK/8ASK/2PSK
            if showMsg:
                print("判断为4ASK/8ASK/2PSK")
            mt=reco_ask(chara.f_x1, askIdealValue, askFreeBias,showMsg)
            if mt==None:
                if showMsg:
                    print("判断为{1}{0}".format("x","x"))
            else:
                if showMsg:
                    print("判断为{1}{0}".format(mt.type,mt.step))
            if mt==MT("ask",2):
                return MT("psk",2)
            else:
                return mt
        else:
            # 4PSK/MQAM
            if showMsg:
                print("C63/C21**3={}".format(chara.C63/chara.C21**3))
            if chara.C63/chara.C21**3<=C63C21_ideal+C63C21_bias and \
                    chara.C63/chara.C21**3>=C63C21_ideal-C63C21_bias:
                if showMsg:
                    print("判断为4PSK")
                return MT("psk",4)
            else:
                if showMsg:
                    print("判断为MQAM")
                return reco_qam(chara.f_x1, qamIdealValue, qamFreeBias,showMsg)

def quickModArr(modType,step,avgTimes=50,bytesLen=300,
                bytesRate:int=100,carrierFreq:int=1000,
                sampleFreq:int=6000,seperate:int=100):
    """获取指定avgTimes行数的调制信号采样矩阵
    输入：调制类型，调制阶数，信号行数，调制码元长度，比特速率，
    载波频率，采样速率，fsk频率间隔
    输出：基带信号，调制信号，时间序列矩阵，每个矩阵的行数均为avgTimes给出的"""
    constelList = []
    signalList = []
    timeSeriesList = []
    for avgT in range(avgTimes):
        bitsData = getBitsData(step, bytesLen)
        constel, signal, timeSeries = quickMod(modType, step, None, bitsData, bytesRate, carrierFreq,
                                               sampleFreq, seperate)
        constelList.append(constel)
        signalList.append(signal)
        timeSeriesList.append(timeSeries)
    constelArr = numpy.array(constelList)
    signalArr = numpy.array(signalList)
    timeSeriesArr = numpy.array(timeSeriesList)
    return constelArr,signalArr,timeSeriesArr

def getRecoRatio_s(constelArr, signalArr, timeSeriesArr, modType, step, snr,
                   runTimes, antArr:bool=False,
                   carrierFreq:int=1000, seperate:int=100,
                   signalWay=30.0, antPairN=8, antDis=0.5, a_t=1.0,
                   noiseRad:float=0.1*numpy.pi,useSigmaafOrc41c202:bool=True):
    """输入基带及调制后信号，获取调制识别率
    注意，该函数为输入给定基带信号、调制信号、时间序列并获取其识别率的函数
    输入：基带信号，调制信号，时间序列矩阵，调制类型，调制阶数，添加噪声信噪比，识别率除数（分母），
    是否采用阵列天线信道，载波频率，fsk频率间隔，使用阵列天线时的三个参数（信号方向，阵元数，阵元间距），
    at判决门限，噪声门限（建议默认），是否使用fsk变换方法后进行识别
    输出：调制识别率"""
    if runTimes>=0:
        correctTimes=0
        for runT in range(runTimes):
            if antArr:
                signalWayArr = numpy.ones(constelArr.shape[0], dtype=numpy.float32) * signalWay
                constelArr_noise = antArrChan(constelArr, snr, signalWayArr, antPairN, antDis)
                signalArr_noise = antArrChan(signalArr, snr, signalWayArr, antPairN, antDis)
            else:
                constelArr_noise = awgn(constelArr, snr)
                signalArr_noise = awgn(signalArr, snr)
            if antArr:
                askIdealValue=numpy.array([32.0,27.4452,27.1459],dtype=numpy.float64)
                qamIdealValue=numpy.array([13.9723,13.7995,13.6408],dtype=numpy.float64)
                mt = recognize(constelArr_noise, signalArr_noise, timeSeriesArr,
                               carrierFreq, seperate, a_t, noiseRad,useSigmaafOrc41c202,
                               askIdealValue=askIdealValue,
                               qamIdealValue=qamIdealValue)
            else:
                mt = recognize(constelArr_noise, signalArr_noise, timeSeriesArr,
                               carrierFreq, seperate, a_t, noiseRad,useSigmaafOrc41c202)
            # mt = recognize_2(constelArr_noise, signalArr_noise, timeSeriesArr, carrierFreq, a_t, noiseRad)
            if mt==None:
                pass
                print("给定{0}{1}，SNR={7}dB，识别{2}{3}，正确次数{6}\n第{4}次，共{5}次".
                      format(step,modType,"x","x",runT+1,runTimes,correctTimes,snr))
            else:
                if mt.type==modType and mt.step==step:
                    correctTimes+=1
                # pbar.update(1)
                print("给定{0}{1}，SNR={7}dB，识别{2}{3}，正确次数{6}\n第{4}次，共{5}次\n".
                      format(step, modType,mt.step, mt.type, runT + 1,runTimes, correctTimes,snr))
        return correctTimes/runTimes
    else:
        raise ValueError("runTimes必须大于0")

def getRecoRatio_s_doa(constelArr, signalArr, timeSeriesArr, modType, step, snr,
                       runTimes, antArr:bool=False,
                       carrierFreq:int=1000, seperate:int=100,
                       doaSignals:int=3, antPairN=8, antDis=0.5, a_t=1.0,
                       noiseRad:float=0.1*numpy.pi,useSigmaafOrc41c202:bool=True):
    """输入基带及调制后信号，获取调制识别率，其中阵列天线信道部分采用多个信源进行空域滤波后获取第一信源信号
    注意，该函数为输入给定基带信号、调制信号、时间序列并获取其识别率的函数
    输入：基带信号，调制信号，时间序列矩阵，调制类型，调制阶数，添加噪声信噪比，识别率除数（分母），
    是否采用阵列天线信道，载波频率，fsk频率间隔，使用阵列天线时的三个参数（信号方向，阵元数，阵元间距），
    at判决门限，噪声门限（建议默认），是否使用fsk变换方法后进行识别
    输出：调制识别率"""
    if runTimes>=0:
        correctTimes=0
        for runT in range(runTimes):
            if antArr:
                constelArr_noise = antArrChan_simpleDoa(constelArr, snr, antPairN, antDis,doaSignals)
                signalArr_noise = antArrChan_simpleDoa(signalArr, snr, antPairN, antDis,doaSignals)
            else:
                constelArr_noise = awgn(constelArr, snr)
                signalArr_noise = awgn(signalArr, snr)
            if antArr:
                askIdealValue=numpy.array([32.0,27.4452,27.1459],dtype=numpy.float64)
                qamIdealValue=numpy.array([13.9723,13.7995,13.6408],dtype=numpy.float64)
                mt = recognize(constelArr_noise, signalArr_noise, timeSeriesArr,
                               carrierFreq, seperate, a_t, noiseRad,useSigmaafOrc41c202,
                               askIdealValue=askIdealValue,
                               qamIdealValue=qamIdealValue,
                               qamFreeBias=4.0)
            else:
                mt = recognize(constelArr_noise, signalArr_noise, timeSeriesArr,
                               carrierFreq, seperate, a_t, noiseRad,useSigmaafOrc41c202)
            # mt = recognize_2(constelArr_noise, signalArr_noise, timeSeriesArr, carrierFreq, a_t, noiseRad)
            if mt==None:
                pass
                print("给定{0}{1}，SNR={7}dB，识别{2}{3}，正确次数{6}\n第{4}次，共{5}次".
                      format(step,modType,"x","x",runT+1,runTimes,correctTimes,snr))
            else:
                if mt.type==modType and mt.step==step:
                    correctTimes+=1
                # pbar.update(1)
                print("给定{0}{1}，SNR={7}dB，识别{2}{3}，正确次数{6}\n第{4}次，共{5}次\n".
                      format(step, modType,mt.step, mt.type, runT + 1,runTimes, correctTimes,snr))
        return correctTimes/runTimes
    else:
        raise ValueError("runTimes必须大于0")

def getRecoRatio(modType,step,snr,runTimes,avgTimes=50,antArr:bool=False,
                 bytesLen=300,bytesRate:int=100,carrierFreq:int=1000,
                 sampleFreq:int=6000,seperate:int=100,
                 signalWay=30.0,antPairN=8,antDis=0.5,a_t=1.0,
                 noiseRad:float=0.1*numpy.pi,useSigmaafOrc41c202:bool=True):
    """直接获取指定调制方式的调制识别率
    注意，该函数为输入调制方式直接获得其调制识别率的函数
    输入：调制类型，调制阶数，添加噪声信噪比，识别率除数（分母），计算特征值时的平均次数，
    是否采用阵列天线信道，调制码元长度，码元速率，载波频率，采样速率，fsk频率间隔，
    使用阵列天线时的三个参数（信号方向，阵元数，阵元间距），
    at判决门限，噪声门限（建议默认），是否使用fsk变换方法后进行识别
    输出：调制识别率"""
    constelArr,signalArr,timeSeriesArr= \
        quickModArr(modType, step, avgTimes, bytesLen,
                    bytesRate, carrierFreq,sampleFreq, seperate)
    return getRecoRatio_s(constelArr, signalArr, timeSeriesArr,
                          modType, step, snr, runTimes, antArr,
                          carrierFreq, seperate, signalWay,
                          antPairN, antDis, a_t, noiseRad,useSigmaafOrc41c202)

def antArrChan_simpleDoa(signal:numpy.ndarray, snr:float,
                         antPairN:int=8,antDis:float=0.5,doaSignals:int=3):
    """简单空域滤波函数，输入signal矩阵，直接输出经空域滤波后的矩阵
    输入：信号矩阵，信噪比，阵列天线阵元数，阵元间距，空域滤波前接收到的信源数"""
    if signal.shape[0]<=doaSignals:
        raise ValueError("输入signal矩阵中信号个数必须大于空域滤波信号源数doaSignals")
    newSignal=numpy.zeros(signal.shape,dtype=signal.dtype)
    signalReal=numpy.isreal(signal).all()
    for sI in range(signal.shape[0]):
        choseSignalsIndex=numpy.random.randint(numpy.ones(doaSignals)*signal.shape[0])
        choseSignalsIndex[0]=sI
        signalDoa=antArrChan_doa(signal[choseSignalsIndex,:],snr,
                       numpy.random.randint(numpy.ones(doaSignals)*-89,numpy.ones(doaSignals)*90),
                       None,antPairN,antDis)
        if signalReal:
            newSignal[sI,:]=numpy.abs(normalize(signalDoa))
        else:
            newSignal[sI,:]=normalize(signalDoa)
    return newSignal

if __name__=="__main__":
    possibleTypes=[MT("ask",4),MT("ask",8),
                   MT("psk",2),MT("psk",4),
                   MT("qam",16),MT("qam",64),MT("qam",256),
                   MT("fsk",2),MT("fsk",4)]
    typeI=5
    ratio=getRecoRatio(possibleTypes[typeI].type,possibleTypes[typeI].step,40,100,antArr=True)
    print(ratio)
